package fem2.level_set;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import math2.FunctionBasisOnRnToR;
import fem2.BasisManager;
import fem2.Element;
import fem2.MeshPart;
import fem2.Model;
import fem2.Node;

/**
 * abstract class for all types of level set with level values defined at node<br>
 * Note:<br>
 * It uses Lagrange type shape functions to interpolate level set value (instead
 * of the one using in geometry of mesh part or basis of the mesh part).
 * Therefore it will not work on mesh part contains non-straight segment between
 * adjacent nodes<br>
 * The current version does not support to store history of level set
 * 
 * @author hbui
 * 
 */
public class LevelSet {

	private Map<Node, Double> nodalLevelValues = new TreeMap<Node, Double>();
	private Set<Element> zeroLevelElements = new TreeSet<Element>();
	private Model m;
	private String name;

	// /**
	// * construct the level set from a set of nodes
	// *
	// * @param nodes
	// */
	// public LevelSet(Node... nodes) {
	// for (Node n : nodes) {
	// update(n, 0.0);
	// }
	// }
	//

	/**
	 * construct the level set from model
	 * 
	 * @param mesh
	 */
	public LevelSet(String name, Model m) {
		this.name = name;
		this.m = m;
		for (int i = 0; i < m.getMesh().countNodes(); i++) {
			update(m.getMesh().getNode(i), 0.0);
		}
	}

	/**
	 * 
	 * @return the name of this level set
	 */
	public String getName() {
		return name;
	}

	/**
	 * get the associated model of this level set
	 * 
	 * @return
	 */
	public Model getModel() {
		return this.m;
	}

	/**
	 * get the set of elements distributed around zero level
	 * 
	 * @return
	 */
	public Set<Element> getZeroLevelElements() {
		return this.zeroLevelElements;
	}

	/**
	 * update value of level set at node
	 * 
	 * @param n
	 * @param value
	 */
	protected void update(Node n, double value) {
		nodalLevelValues.put(n, value);
	}

	/**
	 * check if the element was cut by level set, if yes then add to the set of
	 * zero-level element
	 * 
	 * @param e
	 */
	protected void checkZeroElement(Element e) {
		boolean isCut = true;
		double min = Double.POSITIVE_INFINITY, max = Double.NEGATIVE_INFINITY;
		Node[] nodes = e.getMeshPart().getNodes();
		for (int i = 0; i < nodes.length; i++) {
			double phi = valueAt(nodes[i]);
			if (phi < min) {
				min = phi;
			}
			if (phi > max) {
				max = phi;
			}
		}
		isCut &= (min * max <= 0.0);
		if (isCut) {
			zeroLevelElements.add(e);
		} else {
			zeroLevelElements.remove(e);
		}
	}

	/**
	 * get the level set value at node
	 * 
	 * @param n
	 * @return
	 */
	public double valueAt(Node n) {
		return nodalLevelValues.get(n);
	}

	/**
	 * interpolate value of level set inside a mesh part
	 * 
	 * @param mp
	 * @param xi
	 * @return
	 */
	public double valueAt(MeshPart mp, double... xi) {
		Node[] nodes = mp.getNodes();
		double v = 0.0;
		FunctionBasisOnRnToR basis = BasisManager.getInstance().getBasis(mp.getType());
		for (int i = 0; i < nodes.length; i++) {
			double N = basis.getBasisFunction(i).valueAt(xi);
			v += N * valueAt(nodes[i]);
		}
		return v;
	}

	/**
	 * compute gradient of level set function within a mesh part
	 * 
	 * @param mp
	 * @param xi
	 * @return
	 */
	public double[] gradientAt(MeshPart mp, double... xi) {
		Node[] nodes = mp.getNodes();
		int dim = mp.getDimension();
		double[] G = new double[dim];

		// double[][] grad = mp.gGradientsAt(xi);
		// for (int i = 0; i < grad.length; i++) {
		// double v = valueAt(nodes[i]);
		// for (int j = 0; j < dim; j++) {
		// G[j] += grad[i][j] * v;
		// }
		// }

		FunctionBasisOnRnToR basis = BasisManager.getInstance().getBasis(mp.getType());
		for (int i = 0; i < nodes.length; i++) {
			double v = valueAt(nodes[i]);
			double[] dN = basis.getBasisFunction(i).gradientAt(xi);
			for (int j = 0; j < dim; j++) {
				G[j] += dN[j] * v;
			}
		}

		return G;
	}

}
